package solver;

import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import model.BalanceObjective;
import model.Location;
import model.Machine;
import model.Problem;
import model.Process;
import model.Resource;
import model.Service;
import model.Solution;
import model.SolverEnvironment;
import util.Chronograph;
import com.google.ortools.linearsolver.MPConstraint;
import com.google.ortools.linearsolver.MPSolver;
import com.google.ortools.linearsolver.MPVariable;
import command.IncrementalMoveProcessToMachineCommand;
import etm.core.monitor.EtmPoint;

public class OrtExtraLeanIPModelBuilder extends BaseExtraLeanIPModelBuilder {

	// ######## START OF ORTools VARIABLES#############
	HashMap<Integer, MPVariable>[] x;
	MPVariable[][] y;
	MPVariable[][] k;
	MPVariable[][] lo_cost;
	MPVariable lo_cost_remaining;
	MPVariable[][][] a;
	MPVariable[][] abs;
	MPVariable[][] ba_cost;
	MPVariable ba_cost_remaining;
	MPVariable pm_cost;
	MPVariable[] se;
	MPVariable[] abs_se;
	MPVariable[] se_cost;
	MPVariable mm_cost;
	MPSolver solver;

	double infinity = MPSolver.infinity();
	private boolean saveLP = false;
	private StringBuilder objective_sb = new StringBuilder();

	public OrtExtraLeanIPModelBuilder(Problem problem,
			List<Integer> selectedProcesses, boolean verbose) {
		this(problem, selectedProcesses, verbose, 5, Long.MAX_VALUE);
	}

	public OrtExtraLeanIPModelBuilder(Problem problem,
			List<Integer> selectedProcesses, boolean verbose,
			int timeLimitInSeconds, long cutoff) {
		super(problem, selectedProcesses, verbose, timeLimitInSeconds);
		this.cutoff = cutoff;
		this.timeLimit = timeLimitInSeconds;
		// solver = new MPSolver("RoadDefLinearExample",
		// MPSolver.GLPK_MIXED_INTEGER_PROGRAMMING);
		solver = new MPSolver("RoadDefLinearExample",
				MPSolver.CBC_MIXED_INTEGER_PROGRAMMING);
		// solver.EnableOutput();
		log.debug("Time limit for subproblem is " + timeLimitInSeconds);
		solver.setTimeLimit(1000 * timeLimitInSeconds);
		// solver.init();
		log.debug(solver.solverVersion());
	}

	public boolean solve(boolean saveLP) {
		Chronograph chronos = new Chronograph();
		chronos.reset();
		solver.reset();
		Collections.sort(selectedMachines);
		Collections.sort(selectedProcesses);
		this.saveLP = saveLP;
		boolean flag = false;
		try {
			modelBuild();
			int optimstatus = modelSolve();
			chronos.lap();
			log.debug("Time to solve subproblem was: "
					+ chronos.getElapsedTimeInMilliseconds() + " milliseconds");
			if (optimstatus == MPSolver.INFEASIBLE) {
				solver_status = -1;
				log.debug("Model is infeasible");
				// System.exit(-1);
			} else if (optimstatus == MPSolver.UNBOUNDED) {
				solver_status = -1;
				log.debug("Model is unbounded");
			} else if (optimstatus == MPSolver.ABNORMAL) {
				log.debug("Model is abnormal. Time limit exceeded");
				solver_status = -1;
			} else if (optimstatus == MPSolver.NOT_SOLVED) {
				solver_status = -1;
				log.debug("Model not solved");
			} else if (optimstatus == MPSolver.FEASIBLE) {
				log.debug(String.format("Objective best bound = %f", solver
						.objective().bestBound()));
				log.debug(String.format("Objective value = %f", solver
						.objective().value()));
				if ((int) Math.round(solver.objectiveValue()) == -1) {
					log.debug("Model is feasible but no objective value was returned");
					solver_status = -2;
					// System.exit(-1);
				} else {
					log.debug("Model is feasible :)");
					flag = true;
					solver_status = 1;
				}
			} else if (optimstatus == MPSolver.OPTIMAL) {
				log.debug("Model has been solved to optimality :)");
				flag = true;
				solver_status = 0;
			}
			if (flag) {
				load_cost = computeLoadCost();
				balance_cost = computeBalanceCost();
				process_move_cost = computeProcessMoveCost();
				service_move_cost = computeServiceMoveCost();
				machine_move_cost = computeMachineMoveCost();
				// log.debug(String.format("Objective value (ORT)=%,d",
				// (long) solver.objectiveValue()));
				log.debug(String
						.format("[ORT Solution=%,d] load_cost=%,d balance_cost=%,d process move cost=%,d "
								+ "service move cost=%,d machine move cost=%,d",
								(long) Math.round(solver.objectiveValue()),
								load_cost, balance_cost, process_move_cost,
								service_move_cost, machine_move_cost));
			}
		} catch (Exception ex) {
			ex.printStackTrace();
			flag = false;
		}
		return flag;
	}

	public void modelBuild() {
		// EtmPoint point = etmMonitor
		// .createPoint("OrtExtraLeanIPModelBuilder:modelBuild");

		addVariables();
		setObjectiveCoefficients();
		if (includeHardConstraints[0]) {
			addConstraints0();
		}
		if (includeHardConstraints[1]) {
			addConstraints1();
		}
		if (includeHardConstraints[2]) {
			addConstraints2();
		}
		if (includeHardConstraints[3]) {
			addConstraints3();
		}
		if (includeHardConstraints[4]) {
			addConstraints4();
		}
		if (includeHardConstraints[5]) {
			addConstraints5();
		}

		if (includeSoftConstraints[0]) {
			addSoftConstraints0();
		}
		if (problem.hasBalanceObjectives() && includeSoftConstraints[1]) {
			addSoftConstraints1();
		}
		if (includeSoftConstraints[2]) {
			addSoftConstraints2();
		}
		if (includeSoftConstraints[3]) {
			addSoftConstraints3();
		}
		if (includeSoftConstraints[4]) {
			addSoftConstraints4();
		}

		addCutOffConstraint();
		log.debug(String.format("Cutoff added =%,d", cutoff));

		if (saveLP) {
			ipModelAsString.append("\n");
			addVariablesToIpModelAsString();
			ipModelAsString.append("\n");
			ipModelAsString.append("END\n");
			saveToLP("machine_reassignment_extra_lean_ort.lp");
		}
		// point.collect();
	}

	private int modelSolve() {
		// EtmPoint point = etmMonitor
		// .createPoint("OrtExtraLeanIPModelBuilder:modelSolve");
		// solver.EnableOutput();
		// displaySystemMemoryInfo();
		int optimstatus = solver.solve();
		log.debug("ORT state= " + getORToolsStatus(optimstatus));
		// point.collect();
		return optimstatus;
	}

	private String getORToolsStatus(int optimstatus) {
		if (optimstatus == MPSolver.OPTIMAL)
			return "OPTIMAL";
		if (optimstatus == MPSolver.FEASIBLE)
			return "FEASIBLE";
		else if (optimstatus == MPSolver.INFEASIBLE)
			return "INFEASIBLE";
		else if (optimstatus == MPSolver.UNBOUNDED)
			return "UNBOUNDED";
		else if (optimstatus == MPSolver.ABNORMAL)
			return "ABNORMAL";
		else if (optimstatus == MPSolver.NOT_SOLVED)
			return "NOT SOLVED";
		else
			return "UNKNOWN";
	}

	private void addVariablesToIpModelAsString() {
		ipModelAsString.append("Binaries\n");
		for (Integer p : selectedProcesses) {
			Process aProcess = problem.getProcesses().get(p);
			for (Integer m : aProcess.getEligibleMachines()) {
				ipModelAsString.append(String.format("x%d_%d ", p, m));
			}
		}
	}

	private long computeLoadCost() {
		long sum = 0;
		for (int i = 0; i < selectedMachines.size(); i++) {
			long sum2 = 0;
			for (int r = 0; r < problem.RE; r++) {
				long r_value = Math.round(lo_cost[i][r].solutionValue());
				Resource aResource = problem.getResourceByIndex(r);
				sum2 += r_value * aResource.getWeightLoadCost();
			}
			sum += sum2;
		}
		sum += computeNonSelectedMachinesLoadCost();
		return sum;
	}

	private long computeBalanceCost() {
		long sum = 0;
		int bo_index = 0;
		for (BalanceObjective bo : problem.getBalanceObjectives()) {
			for (int i = 0; i < selectedMachines.size(); i++) {
				long r_value = Math.round(ba_cost[bo_index][i].solutionValue());
				sum += r_value * bo.weightBalanceCost;
			}
		}
		sum += computeNonSelectedMachinesBalanceCost();
		return sum;
	}

	private long computeProcessMoveCost() {
		return (long) Math.round(pm_cost.solutionValue())
				* problem.getWeightProcessMoveCost();
	}

	private long computeServiceMoveCost() {
		if (AS >= 1)
			return (long) Math.round(se_cost[AS - 1].solutionValue())
					* problem.getWeightServiceMoveCost();
		else
			return 0;
	}

	private long computeMachineMoveCost() {
		return (long) Math.round(mm_cost.solutionValue())
				* problem.getWeightMachineMoveCost();
	}

	@SuppressWarnings("unchecked")
	private void addVariables() {
		generateAffectedServicesAndLocations();
		int nop = selectedProcesses.size();
		x = new HashMap[nop];
		for (int i = 0; i < nop; i++) {
			x[i] = new HashMap<Integer, MPVariable>();
			int p = selectedProcesses.get(i);
			Process proc = problem.getProcesses().get(p);
			for (Integer machine_id : proc.getEligibleMachines()) {
				x[i].put(machine_id, solver.makeBoolVar(String.format("x%d_%d",
						p, machine_id)));
			}
		}

		// ##################### SPREAD VARIABLES ####################
		y = new MPVariable[AS][problem.L];
		for (int i = 0; i < AS; i++) {
			for (Integer l : affectedLocations) {
				y[i][l] = solver.makeBoolVar(String.format("y_s%d_l%d",
						affectedServices.get(i), l));
			}
		}

		// ##################### LOAD COST VARIABLES ####################
		int SM = selectedMachines.size();
		k = new MPVariable[SM][problem.RE];
		lo_cost = new MPVariable[SM][problem.RE];
		for (int i = 0; i < SM; i++) {
			int m = selectedMachines.get(i);
			for (int r = 0; r < problem.RE; r++) {
				k[i][r] = solver.makeIntVar(0, infinity,
						String.format("k%d_%d", m, r));
				lo_cost[i][r] = solver.makeIntVar(0, infinity,
						String.format("lc%d_%d", m, r));

			}
		}
		lo_cost_remaining = solver.makeIntVar(0.0, infinity,
				"lo_cost_remaining");

		// ##################### BALANCE COST VARIABLES ####################
		if (problem.hasBalanceObjectives()) {
			a = new MPVariable[problem.BO][SM][2];
			abs = new MPVariable[problem.BO][SM];
			ba_cost = new MPVariable[problem.BO][SM];
			for (int bo_index = 0; bo_index < problem.BO; bo_index++) {
				BalanceObjective bo = problem.getBalanceObjectives().get(
						bo_index);
				for (int i = 0; i < SM; i++) {
					int m = selectedMachines.get(i);
					abs[bo_index][i] = solver.makeIntVar(0.0, infinity,
							String.format("abs_bo%d_m%d", bo_index, m));

					ba_cost[bo_index][i] = solver.makeIntVar(0.0, infinity,
							String.format("ba_cost_bo%d_m%d", bo_index, m));

					a[bo_index][i][0] = solver.makeIntVar(0.0, infinity,
							String.format("a_bo%d_m%d_r%d", bo_index, m,
									bo.resourcea));
					a[bo_index][i][1] = solver.makeIntVar(0.0, infinity,
							String.format("a_bo%d_m%d_r%d", bo_index, m,
									bo.resourceb));
				}
			}

			ba_cost_remaining = solver.makeIntVar(0.0, infinity,
					"ba_cost_remaining");
		}

		// ##################### PROCESS_MOVE_COST_VARIABLE ####################
		pm_cost = solver.makeIntVar(0, infinity, "pm_cost");

		// ##################### SERVICE_MOVE_COST_VARIABLES ###################
		se = new MPVariable[AS + 1];
		abs_se = new MPVariable[AS];
		se_cost = new MPVariable[AS];
		for (int i = 0; i < AS; i++) {
			se[i] = solver.makeIntVar(0.0, infinity,
					String.format("se_s%d", affectedServices.get(i)));
		}
		se[AS] = solver.makeIntVar(0.0, infinity, "se_s_not_selected");

		for (int i = 0; i < AS; i++) {
			abs_se[i] = solver.makeIntVar(0.0, infinity,
					String.format("abs_se_s%d", affectedServices.get(i)));
			if (i == AS - 1) {
				se_cost[i] = solver.makeIntVar(0.0, infinity,
						String.format("se_cost_s%d", affectedServices.get(i)));
			} else {
				se_cost[i] = solver.makeIntVar(0.0, infinity,
						String.format("se_cost_s%d", affectedServices.get(i)));
			}
		}

		// ##################### MACHINE_MOVE_COST_VARIABLE ####################
		mm_cost = solver.makeIntVar(0, infinity, "mm_cost");
	}

	private void setObjectiveCoefficients() {
		if (saveLP) {
			ipModelAsString.append("Minimize\n");
		}

		// ##################### LOAD COST ###################################
		for (int i = 0; i < selectedMachines.size(); i++) {
			int m = selectedMachines.get(i);
			for (int r = 0; r < problem.RE; r++) {
				Resource resource = problem.getResources().get(r);
				solver.setObjectiveCoefficient(lo_cost[i][r],
						resource.getWeightLoadCost());
				if (saveLP) {
					String x = String.format("+ %d lc%d_%d ",
							resource.getWeightLoadCost(), m, r);
					objective_sb.append(x);
					ipModelAsString.append(x);
				}
			}
		}
		solver.setObjectiveCoefficient(lo_cost_remaining, 1.0);
		if (saveLP) {
			String x = String.format("+ %.1f lo_cost_remaining ", 1.0);
			objective_sb.append(x);
			ipModelAsString.append(x);
		}

		// ##################### BALANCE COST ##################################
		if (problem.hasBalanceObjectives()) {
			for (int bo_index = 0; bo_index < problem.BO; bo_index++) {
				BalanceObjective bo = problem.getBalanceObjectives().get(
						bo_index);
				for (int i = 0; i < selectedMachines.size(); i++) {
					int m = selectedMachines.get(i);
					solver.setObjectiveCoefficient(ba_cost[bo_index][i],
							bo.weightBalanceCost);
					if (saveLP) {
						String x = String.format("+ %d ba_cost_bo%d_m%d ",
								bo.weightBalanceCost, bo_index, m);
						objective_sb.append(x);
						ipModelAsString.append(x);
					}
				}
			}

			solver.setObjectiveCoefficient(ba_cost_remaining, 1.0);
			if (saveLP) {
				String x = "+ 1.0 ba_cost_remaining ";
				objective_sb.append(x);
				ipModelAsString.append(x);
			}
		}

		// ##################### PROCESS MOVE COST ##########################
		solver.setObjectiveCoefficient(pm_cost,
				problem.getWeightProcessMoveCost());
		if (saveLP) {
			String x = String.format("+ %d pm_cost ",
					problem.getWeightProcessMoveCost());
			objective_sb.append(x);
			ipModelAsString.append(x);
		}
		// ##################### SERVICE MOVE COST ##########################
		if (AS > 1) {
			solver.setObjectiveCoefficient(se_cost[AS - 1],
					problem.getWeightServiceMoveCost());
			if (saveLP) {
				String x = String.format("+ %d se_cost_s%d ",
						problem.getWeightServiceMoveCost(),
						affectedServices.get(AS - 1));
				objective_sb.append(x);
				ipModelAsString.append(x);
			}
		}
		// ##################### MACHINE MOVE COST ##########################
		solver.setObjectiveCoefficient(mm_cost,
				problem.getWeightMachineMoveCost());
		if (saveLP) {
			String x = String.format("+ %d mm_cost ",
					problem.getWeightMachineMoveCost());
			objective_sb.append(x);
			ipModelAsString.append(x);
		}

		solver.setMinimization();

		if (saveLP) {
			ipModelAsString.append("\n");
			ipModelAsString.append("Subject To");
			ipModelAsString.append("\n");
		}
	}

	private void addCutOffConstraint() {
		StringBuilder sb = new StringBuilder();
		MPConstraint c = solver
				.makeConstraint(0.0, cutoff, "cutoff_constraint");
		for (int i = 0; i < selectedMachines.size(); i++) {
			int m = selectedMachines.get(i);
			for (int r = 0; r < problem.RE; r++) {
				Resource resource = problem.getResources().get(r);
				c.setCoefficient(lo_cost[i][r], resource.getWeightLoadCost());
				if (saveLP) {
					sb.append(String.format(" + %d lc%d_%d ",
							resource.getWeightLoadCost(), m, r));
				}
			}
		}
		c.setCoefficient(lo_cost_remaining, 1.0);
		if (saveLP) {
			sb.append(" + 1.0 lo_cost_remaining ");
		}

		if (problem.hasBalanceObjectives()) {
			for (int bo_index = 0; bo_index < problem.BO; bo_index++) {
				BalanceObjective bo = problem.getBalanceObjectives().get(
						bo_index);
				for (int i = 0; i < selectedMachines.size(); i++) {
					int m = selectedMachines.get(i);
					c.setCoefficient(ba_cost[bo_index][i], bo.weightBalanceCost);
					if (saveLP) {
						sb.append(String.format(" + %d ba_cost_bo%d_m%d ",
								bo.weightBalanceCost, bo_index, m));
					}
				}
			}
			c.setCoefficient(ba_cost_remaining, 1.0);
			if (saveLP) {
				sb.append(" + 1.0 ba_cost_remaining ");
			}
		}

		c.setCoefficient(pm_cost, problem.getWeightProcessMoveCost());
		c.setCoefficient(mm_cost, problem.getWeightMachineMoveCost());
		if (saveLP) {
			sb.append(String.format(" + %d pm_cost ",
					problem.getWeightProcessMoveCost()));
			sb.append(String.format(" + %d mm_cost ",
					problem.getWeightMachineMoveCost()));
		}

		if (AS >= 1) {
			c.setCoefficient(se_cost[AS - 1],
					problem.getWeightServiceMoveCost());
			if (saveLP) {
				sb.append(String.format(" + %d se_cost_s%d ",
						problem.getWeightServiceMoveCost(),
						affectedServices.get(AS - 1)));
			}
		}
		if (saveLP) {
			ipModelAsString.append(String.format(
					"\ncutoff_constraint: %s <= %d", sb.toString(), cutoff));
		}
		// log.debug("cutoff constraint added!");
	}

	// assignment constraints
	private void addConstraints0() {
		// EtmPoint point = etmMonitor
		// .createPoint("OrtExtraLeanIPModelBuilder:addConstraints0");
		for (Integer p : selectedProcesses) {
			MPConstraint c = solver.makeConstraint(1.0, 1.0,
					String.format("assignment_constraint_p%d", p));
			if (saveLP) {
				ipModelAsString.append(String.format(
						"assignment_constraint_p%d: ", p));
			}
			Process aProcess = problem.getProcesses().get(p);
			for (Integer machine_id : aProcess.getEligibleMachines()) {
				// if (getxVar(p, machine_id)==null)
				// System.err.println("adding null value");
				c.setCoefficient(getxVar(p, machine_id), 1.0);
				if (saveLP) {
					ipModelAsString.append(String.format("+ %.1f x%d_%d ", 1.0,
							p, machine_id));
				}
			}
			if (saveLP) {
				ipModelAsString.append(String.format("= %.1f\n", 1.0));
			}
		}
		// log.debug("Constraints0 added");
		// point.collect();
	}

	// capacity constraints
	private void addConstraints1() {
		// EtmPoint point = etmMonitor
		// .createPoint("OrtExtraLeanIPModelBuilder:addConstraints1");
		for (Integer machine_id : selectedMachines) {
			Machine aMachine = problem.getMachines().get(machine_id);
			for (int resource_id = 0; resource_id < problem.RE; resource_id++) {
				int capacity = problem.C[machine_id][resource_id];
				long rhs = capacity;
				boolean termsExist = false;
				int c = 0;
				for (Integer process_id : aMachine.getMoveableProcesses()) {
					c += problem.R[process_id][resource_id];
					termsExist = true;
				}
				for (Integer process_id : current_solution
						.getProcessesForMachine(machine_id)) {
					if (!selectedProcesses.contains(process_id)) {
						rhs -= problem.R[process_id][resource_id];
					}
				}

				if (termsExist && c > rhs) {
					MPConstraint constr = solver.makeConstraint(0, rhs, String
							.format("capacity_constr_m%d_r%d", machine_id,
									resource_id));
					if (saveLP)
						ipModelAsString.append(String.format(
								"capacity_constr_m%d_r%d: ", machine_id,
								resource_id));

					for (Integer process_id : aMachine.getMoveableProcesses()) {
						constr.setCoefficient(getxVar(process_id, machine_id),
								problem.R[process_id][resource_id]);
						if (saveLP)
							ipModelAsString.append(String.format(
									"+ %d x%d_%d ",
									problem.R[process_id][resource_id],
									process_id, machine_id));
					}
					if (saveLP)
						ipModelAsString.append(String.format("<= %d\n", rhs));
				}
			}
		}
		// point.collect();
		// log.debug("Constraints1 added");
	}

	// conflict constraints
	private void addConstraints2() {
		// EtmPoint point = etmMonitor
		// .createPoint("OrtExtraLeanIPModelBuilder:addConstraints2");
		for (Integer s : affectedServices) {
			Service service = problem.getServices().get(s);
			List<Integer> service_processes = service.getProcesses();
			if (service_processes.size() == 1) {
				continue;
			} else {
				for (int machine_id = 0; machine_id < problem.M; machine_id++) {
					StringBuilder sb_expr = new StringBuilder();
					if (saveLP) {
						sb_expr.append(String.format(
								"conflict_constr_s%d_%d: ", s, machine_id));
					}
					int rhs = 1;
					int termsExist = 0;
					for (Integer process_id : service_processes) {
						Process process = problem.getProcesses()
								.get(process_id);
						if (process.getEligibleMachines().contains(
								new Integer(machine_id))) {
							if (getxVar(process_id, machine_id) == null) {
								if (current_solution.getMachineIdForProcessId(
										process_id).intValue() == machine_id) {
									rhs--;
								}
							} else {
								termsExist++;
							}
						} else if (current_solution.getMachineIdForProcessId(
								process_id).intValue() == machine_id) {
							rhs--;
						}
					}
					if (termsExist > rhs) {
						MPConstraint c = solver.makeConstraint(0, rhs,
								String.format("conflict_constr_s%d_%d", s,
										machine_id));
						if (saveLP)
							ipModelAsString.append(sb_expr);
						for (Integer process_id : service_processes) {
							Process process = problem.getProcesses().get(
									process_id);
							if (process.getEligibleMachines().contains(
									new Integer(machine_id))) {
								if (getxVar(process_id, machine_id) != null) {
									c.setCoefficient(
											getxVar(process_id, machine_id),
											1.0);
									if (saveLP)
										ipModelAsString.append(String.format(
												"+ %.1f x%d_%d ", 1.0,
												process_id, machine_id));

								}
							}
						}
						if (saveLP)
							ipModelAsString.append(String
									.format("<= %d\n", rhs));
					}
				}
			}
		}
		// log.debug("Constraints2 added");
		// point.collect();
	}

	// spread constraints
	private void addConstraints3() {
		// EtmPoint point = etmMonitor
		// .createPoint("OrtExtraLeanIPModelBuilder:addConstraints3");
		int k = 0;
		for (Integer s : affectedServices) {
			Service service = problem.getServices().get(s);
			if (service.getSpreadMin() <= 1) {
				k++;
				continue;
			}
			MPConstraint c = solver.makeConstraint(String.format(
					"spread_constr_s%d", s));
			StringBuilder sb_expr = new StringBuilder();
			if (saveLP) {
				sb_expr.append(String.format("spread_constr_s%d ", s));
			}
			int spread_min = service.getSpreadMin();
			for (Location location : problem.getLocations()) {
				int rhs = 0;
				boolean exist = false;
				for (Integer m : location.getMachines()) {
					for (Integer p : service.getProcesses()) {
						MPVariable xVar = getxVar(p, m);
						if (xVar != null) {
							exist = true;
						} else if (current_solution.getMachineIdForProcessId(p)
								.intValue() == m.intValue()) {
							rhs--;
						}
					}
				}

				if (!exist && rhs == 0) {
					continue;
				}
				if (!exist && rhs < 0) {
					spread_min--;
					continue;
				}

				MPConstraint c1 = solver.makeConstraint(
						rhs,
						infinity,
						String.format("spread_constr_s%d_l%d_a", s,
								location.getId()));
				StringBuilder sb_expr1 = new StringBuilder();
				if (saveLP) {
					sb_expr1.append(String.format("spread_constr_s%d_l%d_a ",
							s, location.getId()));
				}
				MPConstraint c2 = solver.makeConstraint(
						-infinity,
						rhs,
						String.format("spread_constr_s%d_l%d_b", s,
								location.getId()));
				StringBuilder sb_expr2 = new StringBuilder();
				if (saveLP) {
					sb_expr2.append(String.format("spread_constr_s%d_l%d_b ",
							s, location.getId()));
				}
				for (Integer m : location.getMachines()) {
					for (Integer p : service.getProcesses()) {
						MPVariable xVar = getxVar(p, m);
						if (xVar != null) {
							c1.setCoefficient(xVar, 1.0);
							if (saveLP) {
								sb_expr1.append(String.format("+ %.1f x%d_%d ",
										1.0, p, m));
							}
							c2.setCoefficient(xVar, 1.0);
							if (saveLP) {
								sb_expr2.append(String.format("+ %.1f x%d_%d ",
										1.0, p, m));
							}
						}
					}
				}
				c1.setCoefficient(y[k][location.getId()], -1.0);
				if (saveLP) {
					sb_expr1.append(String.format("+ %.1f y_s%d_l%d ", -1.0,
							affectedServices.get(k), location.getId()));
				}
				c2.setCoefficient(y[k][location.getId()], -location
						.getMachines().size());
				if (saveLP) {
					sb_expr2.append(String.format("+ %d y_s%d_l%d ", -location
							.getMachines().size(), affectedServices.get(k),
							location.getId()));
					sb_expr1.append(String.format(">= %d\n", rhs));
					ipModelAsString.append(sb_expr1.toString());
					sb_expr2.append(String.format("<= %d\n", rhs));
					ipModelAsString.append(sb_expr2.toString());
				}
				c.setCoefficient(y[k][location.getId()], 1.0);

				if (saveLP) {
					sb_expr.append(String.format("+ %.1f y_s%d_l%d ", 1.0,
							affectedServices.get(k), location.getId()));
				}
			}
			c.setBounds(spread_min, infinity);
			if (saveLP) {
				sb_expr.append(String.format(">= %d\n", spread_min));
				ipModelAsString.append(sb_expr.toString());
			}
			k++;
		}
		// log.debug("Constraints3 added");
		// point.collect();
	}

	// Dependency constraints
	private void addConstraints4() {
		// EtmPoint point = etmMonitor
		// .createPoint("OrtExtraLeanIPModelBuilder:addConstraints4");
		Chronograph chronos = new Chronograph();
		chronos.reset();
		int counters[] = { 0, 0, 0 };
		Map<String, Integer> type4constraints = new HashMap<String, Integer>();
		List<String> sb_nList = new ArrayList<String>();
		for (Integer sa_index : offendedServices) {
			Service servicea = problem.getServices().get(sa_index);
			for (Integer pa_index : servicea.getProcesses()) {
				for (Integer neighborhood_id : offendedNeighborhoods) {
					int r = 0;
					List<String> plusXvarsList = new ArrayList<String>();
					boolean plusXExist = false;
					String key1 = String.format("%d_%d", pa_index,
							neighborhood_id);
					boolean plusXexistsHavingValue1InCurrentSolution = false;
					if (selected_XVARS_per_process_neighborhood
							.containsKey(key1)) {
						for (String xvar_s : selected_XVARS_per_process_neighborhood
								.get(key1)) {
							String s[] = xvar_s.split("_");
							int pa_id = Integer.parseInt(s[0]);
							int m_id = Integer.parseInt(s[1]);
							plusXvarsList.add(xvar_s);
							plusXExist = true;
							counters[0]++;
							if (current_solution.getMachineIdForProcessId(
									pa_index).intValue() == m_id)
								plusXexistsHavingValue1InCurrentSolution = true;
						}
					}
					if (current_solution.getNeighBorhoodIdForProcess(pa_index) == neighborhood_id) {
						if (plusXexistsHavingValue1InCurrentSolution == false)
							r = -1;
					}
					if ((r == 0) && (plusXExist == false))
						continue;

					for (Integer sb_index : servicea.getDependsOn()) {
						String key2 = String.format("%d_%d", sb_index,
								neighborhood_id);
						if (sb_nList.contains(key2) && plusXExist == false)
							continue;
						int rhs = r;
						List<String> minusXvarsList = new ArrayList<String>();
						boolean minusXExists = false;
						int c = current_solution.serviceProcessesPerNeighborhood[sb_index][neighborhood_id];
						if (selected_XVARS_per_service_neighborhood
								.containsKey(key2)) {
							for (String xvar_s : selected_XVARS_per_service_neighborhood
									.get(key2)) {
								String s[] = xvar_s.split("_");
								int pa_id = Integer.parseInt(s[0]);
								int m_id = Integer.parseInt(s[1]);
								minusXvarsList.add(xvar_s);
								minusXExists = true;
								counters[1]++;
								if (current_solution.getMachineIdForProcessId(
										pa_id).intValue() == m_id)
									c--;
							}
						}
						rhs += c;
						if (!plusXExist && minusXExists && rhs >= 0)
							continue;
						if ((plusXExist || minusXExists) && rhs <= 0) {
							counters[2]++;
							StringBuilder sb = new StringBuilder();
							for (String xVar : plusXvarsList) {
								sb.append(" + " + xVar);
							}
							for (String xVar : minusXvarsList) {
								sb.append(" - " + xVar);
							}
							if (type4constraints.containsKey(sb.toString())) {
								int existing_rhs = type4constraints.get(sb
										.toString());
								if (existing_rhs <= rhs) {
									// log.info(String.format("Constraint %s <= %d skipped",
									// sb.toString(), rhs));
									continue;
								} else {
									type4constraints.put(sb.toString(), rhs);
									// log.info(String.format("RHS for %s has been replaced from %d to %d",
									// sb.toString(), existing_rhs, rhs));
								}
							} else {
								type4constraints.put(sb.toString(), rhs);
							}
							MPConstraint constr = solver.makeConstraint(
									-infinity, rhs, String.format(
											"depends_constr_sa%d_sb%d_p%d_n%d",
											sa_index, sb_index, pa_index,
											neighborhood_id));
							if (saveLP) {
								ipModelAsString.append(String.format(
										"depends_constr_sa%d_sb%d_p%d_n%d: ",
										sa_index, sb_index, pa_index,
										neighborhood_id));
							}
							for (String xvar_s : plusXvarsList) {
								String s[] = xvar_s.split("_");
								int pa_id = Integer.parseInt(s[0]);
								int m_id = Integer.parseInt(s[1]);
								MPVariable xVar = getxVar(pa_id, m_id);
								constr.setCoefficient(xVar, 1.0);
								if (saveLP) {
									ipModelAsString
											.append(String.format(
													"+ %.1f x%d_%d ", 1.0,
													pa_id, m_id));
								}
							}
							for (String xvar_s : minusXvarsList) {
								String s[] = xvar_s.split("_");
								int pb_id = Integer.parseInt(s[0]);
								int m_id = Integer.parseInt(s[1]);
								MPVariable xVar = getxVar(pb_id, m_id);
								constr.setCoefficient(xVar, -1.0);
								if (saveLP) {
									ipModelAsString.append(String
											.format("+ %.1f x%d_%d ", -1.0,
													pb_id, m_id));
								}
							}
							if (saveLP) {
								ipModelAsString.append(String.format("<= %d\n",
										rhs));
							}
							if (plusXExist == false)
								sb_nList.add(key2);
						}

					}
				}
			}
		}
		chronos.lap();
		// log.debug(String.format("Time to build Constraint4 was %,d ms %s",
		// chronos.getElapsedTimeInMilliseconds(),
		// Arrays.toString(counters)));
		// log.debug("Constraints4 added");
		// point.collect();
	}

	// Transient constraints
	private void addConstraints5() {
		// EtmPoint point = etmMonitor
		// .createPoint("OrtExtraLeanIPModelBuilder:addConstraints5");
		for (int i = 0; i < problem.TRE.length; i++) {
			int r = problem.TRE[i];
			for (Integer m : selectedMachines) {
				boolean termsExist = false;
				int capacity = problem.RC[m][i];
				long c = 0;
				for (Integer p : current_solution.getProcessesForMachine(m)) {
					if (initial_solution.getMachineIdForProcessId(p).intValue() == m
							.intValue())
						continue;
					if (selectedProcesses.contains(p))
						continue;
					capacity -= problem.R[p][r];
				}

				List<String> xVarsList = new ArrayList<String>();
				for (Integer p : selectedProcesses) {
					if (initial_solution.getMachineIdForProcessId(p).intValue() == m
							.intValue())
						continue;
					MPVariable xVar = getxVar(p, m);
					if (xVar != null) {
						String xvar_s = String.format("%d_%d", p, m);
						xVarsList.add(xvar_s);
						c += problem.R[p][r];
						termsExist = true;
					}
				}
				if (termsExist & c > capacity) {
					MPConstraint constr = solver.makeConstraint(-infinity,
							capacity,
							String.format("transient_constr_m%d_r%d", m, r));
					if (saveLP) {
						ipModelAsString.append(String.format(
								"transient_constr_m%d_r%d: ", m, r));
					}
					for (String xvar_s : xVarsList) {
						String s[] = xvar_s.split("_");
						int p = Integer.parseInt(s[0]);
						MPVariable xVar = getxVar(p, m);
						constr.setCoefficient(xVar, problem.R[p][r]);
						if (saveLP) {
							ipModelAsString.append(String.format(
									" + %d x%d_%d ", problem.R[p][r], p, m));
						}
					}

					if (saveLP) {
						ipModelAsString.append(String.format("<= %d\n",
								capacity));
					}
				}

			}
		}
		// log.debug("Constraint5 added");
		// point.collect();
	}

	// load cost
	private void addSoftConstraints0() {
		// EtmPoint point = etmMonitor
		// .createPoint("OrtExtraLeanIPModelBuilder:addSoftConstraints0");
		for (int r = 0; r < problem.RE; r++) {
			for (int i = 0; i < selectedMachines.size(); i++) {
				int m = selectedMachines.get(i);
				List<MPVariable> xvarsList = new ArrayList<MPVariable>();
				List<Integer> resourceCapacityList = new ArrayList<Integer>();
				List<Integer> processesList = new ArrayList<Integer>();
				for (Integer p : selectedProcesses) {
					MPVariable xVar = getxVar(p, m);
					if (xVar != null) {
						xvarsList.add(xVar);
						resourceCapacityList.add(problem.R[p][r]);
						processesList.add(p);
					}
				}
				if (!xvarsList.isEmpty()) {
					int safety_capacity = problem.getMachines().get(m)
							.getSafetyCapacityFor("r" + r);
					long rhs1 = safety_capacity * 2;
					long rhs2 = 0;
					long rhs3 = 0;
					MPConstraint c1 = solver.makeConstraint(String.format(
							"load_cost_constr_m%d_r%d_a", m, r));
					StringBuilder sb_expr1 = new StringBuilder();
					MPConstraint c2 = solver.makeConstraint(String.format(
							"load_cost_constr_m%d_r%d_b", m, r));
					StringBuilder sb_expr2 = new StringBuilder();
					MPConstraint c3 = solver.makeConstraint(String.format(
							"load_cost_constr_m%d_r%d_c", m, r));
					StringBuilder sb_expr3 = new StringBuilder();
					if (saveLP) {
						sb_expr1.append(String.format(
								"load_cost_constr_m%d_r%d_a: ", m, r));
						sb_expr2.append(String.format(
								"load_cost_constr_m%d_r%d_b: ", m, r));
						sb_expr3.append(String.format(
								"load_cost_constr_m%d_r%d_c: ", m, r));
					}
					int ri = 0;
					for (MPVariable xVar : xvarsList) {
						int resource_capacity = resourceCapacityList.get(ri);
						int p = processesList.get(ri);
						c1.setCoefficient(xVar, resource_capacity);
						c2.setCoefficient(xVar, -resource_capacity);
						c3.setCoefficient(xVar, resource_capacity);
						if (saveLP) {
							sb_expr1.append(String.format(" + %.1f x%d_%d ",
									resource_capacity / 1.0, p, m));
							sb_expr2.append(String.format(" + %d x%d_%d ",
									-resource_capacity, p, m));
							sb_expr3.append(String.format(" + %d x%d_%d ",
									resource_capacity, p, m));
						}
						ri++;
					}
					for (Integer p : current_solution.getProcessesForMachine(m)) {
						if (!selectedProcesses.contains(p)) {
							rhs1 -= problem.R[p][r];
							rhs2 += problem.R[p][r];
							rhs3 -= problem.R[p][r];
						}
					}
					c1.setCoefficient(k[i][r], 1.0);
					c1.setCoefficient(lo_cost[i][r], -2.0);
					c2.setCoefficient(k[i][r], 1.0);
					c3.setCoefficient(k[i][r], 1.0);

					c1.setBounds(rhs1, rhs1);
					c2.setBounds(rhs2, infinity);
					c3.setBounds(rhs3, infinity);
					if (saveLP) {
						sb_expr1.append(String.format(" + %.1f k%d_%d ", 1.0,
								m, r));
						sb_expr1.append(String.format(" + %.1f lc%d_%d ", -2.0,
								m, r));
						sb_expr2.append(String.format(" + %.1f k%d_%d ", 1.0,
								m, r));
						sb_expr1.append(String.format("= %d\n", rhs1));
						sb_expr3.append(String.format(" + %.1f k%d_%d ", 1.0,
								m, r));
						sb_expr2.append(String.format(">= %d\n", rhs2));
						sb_expr3.append(String.format(">= %d\n", rhs3));
						ipModelAsString.append(sb_expr1);
						ipModelAsString.append(sb_expr2);
						ipModelAsString.append(sb_expr3);
					}
				}
			}
		}
		long lo_cost_nsm = computeNonSelectedMachinesLoadCost();
		MPConstraint c = solver.makeConstraint(lo_cost_nsm, lo_cost_nsm,
				"load_cost_constr_not_selected_machines");
		c.setCoefficient(lo_cost_remaining, 1.0);
		if (saveLP) {
			ipModelAsString
					.append(String
							.format("load_cost_constr_not_selected_machines: lo_cost_remaining = %d\n",
									lo_cost_nsm));
		}
		// log.debug("Soft Constraints0 added");
		// point.collect();
	}

	// balance cost
	private void addSoftConstraints1() {
		// EtmPoint point = etmMonitor
		// .createPoint("OrtExtraLeanIPModelBuilder:addSoftConstraints1");
		int bo_index = 0;
		for (BalanceObjective bo : problem.getBalanceObjectives()) {
			for (int i = 0; i < selectedMachines.size(); i++) {
				int m = selectedMachines.get(i);
				boolean exists = false;
				int capacity_a = problem.getMachines().get(m)
						.getCapacityFor("r" + bo.resourcea);
				int capacity_b = problem.getMachines().get(m)
						.getCapacityFor("r" + bo.resourceb);
				int rhs_a = capacity_a;
				int rhs_b = capacity_b;
				for (Integer p : selectedProcesses) {
					MPVariable xVar = getxVar(p, m);
					if (xVar != null) {
						exists = true;
					} else {
						if (current_solution.getMachineIdForProcessId(p) == m) {
							rhs_a -= problem.R[p][bo.resourcea];
							rhs_b -= problem.R[p][bo.resourceb];
						}
					}
				}
				for (Integer p : current_solution.getProcessesForMachine(m)) {
					if (!selectedProcesses.contains(p)) {
						rhs_a -= problem.R[p][bo.resourcea];
						rhs_b -= problem.R[p][bo.resourceb];
					}
				}
				if (exists) {
					MPConstraint c1a = solver
							.makeConstraint(rhs_a, rhs_a, String.format(
									"balance_cost_constr_bo%d_m%d_r%d_a%d_%d",
									bo_index, m, bo.resourcea, m, bo.resourcea));
					MPConstraint c1b = solver
							.makeConstraint(rhs_b, rhs_b, String.format(
									"balance_cost_constr_bo%d_m%d_r%d_a%d_%d",
									bo_index, m, bo.resourceb, m, bo.resourceb));
					StringBuilder sb_expr1a = new StringBuilder();
					StringBuilder sb_expr1b = new StringBuilder();
					if (saveLP) {
						sb_expr1a.append(String.format(
								"balance_cost_constr_bo%d_m%d_r%d_a%d_%d: ",
								bo_index, m, bo.resourcea, m, bo.resourcea));
						sb_expr1b.append(String.format(
								"balance_cost_constr_bo%d_m%d_r%d_a%d_%d: ",
								bo_index, m, bo.resourceb, m, bo.resourceb));
					}
					for (Integer p : selectedProcesses) {
						MPVariable xVar = getxVar(p, m);
						if (xVar != null) {
							exists = true;
							c1a.setCoefficient(xVar, problem.R[p][bo.resourcea]);
							c1b.setCoefficient(xVar, problem.R[p][bo.resourceb]);
							if (saveLP) {
								sb_expr1a.append(String.format(" + %d x%d_%d ",
										problem.R[p][bo.resourcea], p, m));
								sb_expr1b.append(String.format(" + %d x%d_%d ",
										problem.R[p][bo.resourceb], p, m));
							}
						}
					}
					c1a.setCoefficient(a[bo_index][i][0], 1.0);
					c1b.setCoefficient(a[bo_index][i][1], 1.0);
					if (saveLP) {
						sb_expr1a
								.append(String.format(
										" + %.1f a_bo%d_m%d_r%d ", 1.0,
										bo_index, m, 0));
						sb_expr1a.append(String.format("= %d\n", rhs_a));
						ipModelAsString.append(sb_expr1a);
						sb_expr1b
								.append(String.format(
										" + %.1f a_bo%d_m%d_r%d ", 1.0,
										bo_index, m, 1));
						sb_expr1b.append(String.format("= %d\n", rhs_b));
						ipModelAsString.append(sb_expr1b);
					}

					MPConstraint c2 = solver.makeConstraint(0.0, 0.0, String
							.format("balance_cost_constr_bo%d_m%d_a", bo_index,
									m));
					StringBuilder sb_expr2 = new StringBuilder();
					if (saveLP) {
						sb_expr2.append(String
								.format("balance_cost_constr_bo%d_m%d_a: ",
										bo_index, m));
					}
					c2.setCoefficient(a[bo_index][i][0], bo.target / 2.0);
					if (saveLP) {
						sb_expr2.append(String.format(
								" + %.1f a_bo%d_m%d_r%d ", bo.target / 2.0,
								bo_index, m, 0));
					}
					c2.setCoefficient(a[bo_index][i][1], -0.5);
					if (saveLP) {
						sb_expr2.append(String
								.format(" + %.1f a_bo%d_m%d_r%d ", -0.5,
										bo_index, m, 1));
					}
					c2.setCoefficient(abs[bo_index][i], 0.5);
					if (saveLP) {
						sb_expr2.append(String.format(" + %.1f abs_bo%d_m%d ",
								0.5, bo_index, m));
					}
					c2.setCoefficient(ba_cost[bo_index][i], -1.0);
					if (saveLP) {
						sb_expr2.append(String.format(
								" + %.1f ba_cost_bo%d_m%d ", -1.0, bo_index, m));
						sb_expr2.append(String.format("= %.1f\n", 0.0));
						ipModelAsString.append(sb_expr2);
					}
					MPConstraint c3 = solver.makeConstraint(0.0, infinity,
							String.format("balance_cost_constr_bo%d_m%d_b",
									bo_index, m));
					StringBuilder sb_expr3 = new StringBuilder();
					if (saveLP) {
						sb_expr3.append(String
								.format("balance_cost_constr_bo%d_m%d_b: ",
										bo_index, m));
					}
					c3.setCoefficient(abs[bo_index][i], 1.0);
					if (saveLP) {
						sb_expr3.append(String.format(" + %.1f abs_bo%d_m%d ",
								1.0, bo_index, m));
					}
					c3.setCoefficient(a[bo_index][i][0], bo.target);
					if (saveLP) {
						sb_expr3.append(String.format(" + %d a_bo%d_m%d_r%d ",
								bo.target, bo_index, m, 0));
					}
					c3.setCoefficient(a[bo_index][i][1], -1.0);
					if (saveLP) {
						sb_expr3.append(String.format(" %.1f a_bo%d_m%d_r%d ",
								-1.0, bo_index, m, 1));
						sb_expr3.append(String.format(">= %.1f\n", 0.0));
						ipModelAsString.append(sb_expr3);
					}
					MPConstraint c4 = solver.makeConstraint(0.0, infinity,
							String.format("balance_cost_constr_bo%d_m%d_c",
									bo_index, m));
					StringBuilder sb_expr4 = new StringBuilder();
					if (saveLP) {
						sb_expr4.append(String
								.format("balance_cost_constr_bo%d_m%d_c: ",
										bo_index, m));
					}
					c4.setCoefficient(abs[bo_index][i], 1.0);
					if (saveLP) {
						sb_expr4.append(String.format(" + %.1f abs_bo%d_m%d ",
								1.0, bo_index, m));
					}
					c4.setCoefficient(a[bo_index][i][0], -bo.target);
					if (saveLP) {
						sb_expr4.append(String.format(" + %d a_bo%d_m%d_r%d ",
								-bo.target, bo_index, m, 0));
					}
					c4.setCoefficient(a[bo_index][i][1], 1.0);
					if (saveLP) {
						sb_expr4.append(String.format(
								" + %.1f a_bo%d_m%d_r%d ", 1.0, bo_index, m, 1));
						sb_expr4.append(String.format(">= %.1f\n", 0.0));
						ipModelAsString.append(sb_expr4);
					}
				}
			}
			bo_index++;
		}
		long ba_cost_nsm = computeNonSelectedMachinesBalanceCost();
		MPConstraint c = solver.makeConstraint(ba_cost_nsm, ba_cost_nsm,
				"balance_cost_constr_not_selected_machines");
		c.setCoefficient(ba_cost_remaining, 1.0);
		if (saveLP) {
			ipModelAsString
					.append(String
							.format("balance_cost_constr_not_selected_machines: ba_cost_remaining = %d\n",
									ba_cost_nsm));
		}
		// log.debug("Soft Constraints 1 added");
		// point.collect();
	}

	// process move cost
	private void addSoftConstraints2() {
		// EtmPoint point = etmMonitor
		// .createPoint("OrtExtraLeanIPModelBuilder:addSoftConstraints2");
		int sum = 0;
		for (int p = 0; p < problem.P; p++) {
			int pmc = problem.getProcesses().get(p).getProcessMoveCost();
			if (selectedProcesses.contains(p)) {
				for (int m = 0; m < problem.M; m++) {
					MPVariable xVar = getxVar(p, m);
					if (xVar == null) {
						if (initial_solution.getMachineIdForProcessId(p)
								.intValue() == m) {
							sum += pmc;
						}
					} else {
						if ((initial_solution.getMachineIdForProcessId(p)
								.intValue() == m)) {
							sum += pmc;
						}
					}
				}
			} else {
				if (initial_solution.getMachineIdForProcessId(p).intValue() != current_solution
						.getMachineIdForProcessId(p).intValue()) {
					sum += pmc * 2;
				}
			}
		}
		MPConstraint c = solver.makeConstraint(-sum, -sum, "process_move_cost");
		if (saveLP) {
			ipModelAsString.append(String.format("process_move_cost: "));
		}
		for (int p = 0; p < problem.P; p++) {
			int pmc = problem.getProcesses().get(p).getProcessMoveCost();
			if (selectedProcesses.contains(p)) {
				for (int m = 0; m < problem.M; m++) {
					MPVariable xVar = getxVar(p, m);
					if (xVar == null) {
						// do nothing
					} else {
						if ((initial_solution.getMachineIdForProcessId(p)
								.intValue() == m)) {
							c.setCoefficient(xVar, -pmc);
							if (saveLP) {
								ipModelAsString.append(String.format(
										" + %d x%d_%d ", -pmc, p, m));
							}
						} else {
							c.setCoefficient(xVar, pmc);
							if (saveLP) {
								ipModelAsString.append(String.format(
										" + %d x%d_%d ", pmc, p, m));
							}
						}
					}
				}
			}
		}
		c.setCoefficient(pm_cost, -2.0);
		if (saveLP) {
			ipModelAsString.append(String.format(" + %.1f pm_cost ", -2.0,
					pm_cost));
			ipModelAsString.append(String.format("= %d\n", -sum));
		}
		// log.debug("Soft Constraints 2 added");
		// point.collect();
	}

	// service move cost
	private void addSoftConstraints3() {
		// EtmPoint point = etmMonitor
		// .createPoint("OrtExtraLeanIPModelBuilder:addSoftConstraints3");
		if (problem.S <= 1) {
			return;
		}
		if (affectedServices.size() <= 1)
			return;
		long rhs0 = computeNonSelectedMachinesServiceMoveCost();
		MPConstraint c0 = solver.makeConstraint(rhs0, rhs0,
				"service_move_cost_constr_not_selected");
		c0.setCoefficient(se[AS], 1.0);
		if (saveLP) {
			ipModelAsString
					.append(String
							.format("service_move_cost_constr_not_selected: se_snot_selected = %d\n",
									rhs0));
		}
		for (Integer s : affectedServices) {
			boolean exists = false;
			Service service = problem.getServices().get(s);
			int rhs = -service.getProcesses().size();
			for (Integer p : service.getProcesses()) {
				if (selectedProcesses.contains(p)) {
					Process aProcess = problem.getProcesses().get(p);
					for (Integer m : aProcess.getEligibleMachines()) {
						exists = true;
					}
				} else {
					if (initial_solution.getMachineIdForProcessId(p).intValue() != current_solution
							.getMachineIdForProcessId(p).intValue())
						rhs--;
					else
						rhs++;
				}
			}
			if (exists) {
				MPConstraint c = solver.makeConstraint(rhs, rhs,
						String.format("service_move_cost_constr_s%d", s));
				StringBuilder sb_expr = new StringBuilder();
				if (saveLP) {
					sb_expr.append(String.format(
							"service_move_cost_constr_s%d: ", s));
				}
				for (Integer p : service.getProcesses()) {
					if (selectedProcesses.contains(p)) {
						Process aProcess = problem.getProcesses().get(p);
						for (Integer m : aProcess.getEligibleMachines()) {
							MPVariable xVar = getxVar(p, m);
							if (xVar != null) {
								if (initial_solution
										.getMachineIdForProcessId(p) == m) {
									c.setCoefficient(xVar, -1.0);
									if (saveLP) {
										sb_expr.append(String.format(
												" + %.1f x%d_%d ", -1.0, p, m));
									}
								} else {
									c.setCoefficient(xVar, 1.0);
									if (saveLP) {
										sb_expr.append(String.format(
												" + %.1f x%d_%d ", 1.0, p, m));
									}
								}
							}
						}
					}
				}

				int index_of_service = affectedServices.indexOf(s);
				c.setCoefficient(se[index_of_service], -2.0);
				if (saveLP) {
					sb_expr.append(String.format(" + %.1f se_s%d ", -2.0, s));
					sb_expr.append(String.format("= %d\n", rhs));
					ipModelAsString.append(sb_expr);
				}
			}
		}
		int first_service_id = affectedServices.get(0);
		int second_service_id = affectedServices.get(1);
		MPConstraint c1 = solver.makeConstraint(0, 0, String.format(
				"service_move_cost_constr_max_s%d_a", first_service_id));
		StringBuilder sb_expr1 = new StringBuilder();
		if (saveLP) {
			sb_expr1.append(String.format(
					"service_move_cost_constr_max_s%d_a: ", first_service_id));
		}
		c1.setCoefficient(se[0], 0.5);
		if (saveLP) {
			sb_expr1.append(String.format(" + %.1f se_s%d ", 0.5,
					first_service_id));
		}
		c1.setCoefficient(se[1], 0.5);
		if (saveLP) {
			sb_expr1.append(String.format(" + %.1f se_s%d ", 0.5,
					second_service_id));
		}
		c1.setCoefficient(abs_se[0], 0.5);
		if (saveLP) {
			sb_expr1.append(String.format(" + %.1f abs_se_s%d ", 0.5,
					first_service_id));
		}
		c1.setCoefficient(se_cost[0], -1.0);
		if (saveLP) {
			sb_expr1.append(String.format(" + %.1f se_cost_s%d ", -1.0,
					first_service_id));
			sb_expr1.append(String.format("= %d\n", 0));
			ipModelAsString.append(sb_expr1);
		}
		MPConstraint c2 = solver.makeConstraint(0, infinity, String.format(
				"service_move_cost_constr_max_s%d_b", first_service_id));
		StringBuilder sb_expr2 = new StringBuilder();
		if (saveLP) {
			sb_expr2.append(String.format(
					"service_move_cost_constr_max_s%d_b: ", first_service_id));
		}
		c2.setCoefficient(abs_se[0], 1.0);

		if (saveLP) {
			sb_expr2.append(String.format(" + %.1f abs_se_s%d ", 1.0,
					first_service_id));
		}
		c2.setCoefficient(se[0], 1.0);
		if (saveLP) {
			sb_expr2.append(String.format(" + %.1f se_s%d ", 1.0,
					first_service_id));
		}
		c2.setCoefficient(se[1], -1.0);
		if (saveLP) {
			sb_expr2.append(String.format(" + %.1f se_s%d ", -1.0,
					second_service_id));
			sb_expr2.append(String.format(">= %d\n", 0));
			ipModelAsString.append(sb_expr2);
		}
		MPConstraint c3 = solver.makeConstraint(0, infinity, String.format(
				"service_move_cost_constr_max_s%d_c", first_service_id));
		StringBuilder sb_expr3 = new StringBuilder();
		if (saveLP) {
			sb_expr3.append(String.format(
					"service_move_cost_constr_max_s%d_c: ", first_service_id));
		}
		c3.setCoefficient(abs_se[0], 1.0);
		if (saveLP) {
			sb_expr3.append(String.format(" + %.1f abs_se_s%d ", 1.0,
					first_service_id));
		}
		c3.setCoefficient(se[0], -1.0);
		if (saveLP) {
			sb_expr3.append(String.format(" + %.1f se_s%d ", -1.0,
					first_service_id));
		}
		c3.setCoefficient(se[1], 1.0);
		if (saveLP) {
			sb_expr3.append(String.format(" + %.1f se_s%d ", 1.0,
					second_service_id));
			sb_expr3.append(String.format(">= %d\n", 0));
			ipModelAsString.append(sb_expr3);
		}
		for (int i = 2; i <= AS; i++) {
			int iminus2_service_id = affectedServices.get(i - 2);
			int iminus1_service_id = affectedServices.get(i - 1);
			String i_service_id;
			if (i < AS) {
				i_service_id = String.valueOf(affectedServices.get(i));
			} else {
				i_service_id = "not_selected";
			}
			MPConstraint c4 = solver.makeConstraint(0, 0, String.format(
					"service_move_cost_constr_max_s%s_a", i_service_id));
			StringBuilder sb_expr4 = new StringBuilder();
			if (saveLP) {
				sb_expr4.append(String.format(
						"service_move_cost_constr_max_s%s_a: ", i_service_id));
			}
			c4.setCoefficient(se_cost[i - 2], 0.5);
			if (saveLP) {
				sb_expr4.append(String.format(" + %.1f se_cost_s%d ", 0.5,
						iminus2_service_id));
			}
			c4.setCoefficient(se[i], 0.5);
			if (saveLP) {
				sb_expr4.append(String.format(" + %.1f se_s%s ", 0.5,
						i_service_id));
			}
			c4.setCoefficient(abs_se[i - 1], 0.5);
			if (saveLP) {
				sb_expr4.append(String.format(" + %.1f abs_se_s%d ", 0.5,
						iminus1_service_id));
			}
			c4.setCoefficient(se_cost[i - 1], -1.0);
			if (saveLP) {
				sb_expr4.append(String.format(" + %.1f se_cost_s%d ", -1.0,
						iminus1_service_id));
				sb_expr4.append(String.format("= %d\n", 0));
				ipModelAsString.append(sb_expr4);
			}
			MPConstraint c5 = solver.makeConstraint(0, infinity, String.format(
					"service_move_cost_constr_max_s%s_b", i_service_id));
			StringBuilder sb_expr5 = new StringBuilder();
			if (saveLP) {
				sb_expr5.append(String.format(
						"service_move_cost_constr_max_s%s_b: ", i_service_id));
			}
			c5.setCoefficient(abs_se[i - 1], 1.0);
			if (saveLP) {
				sb_expr5.append(String.format(" + %.1f abs_se_s%d ", 1.0,
						iminus1_service_id));
			}
			c5.setCoefficient(se_cost[i - 2], 1.0);
			if (saveLP) {
				sb_expr5.append(String.format(" + %.1f se_cost_s%d ", 1.0,
						iminus2_service_id));
			}
			c5.setCoefficient(se[i], -1.0);
			if (saveLP) {
				sb_expr5.append(String.format(" + %.1f se_s%s ", -1.0,
						i_service_id));
				sb_expr5.append(String.format(">= %d\n", 0));
				ipModelAsString.append(sb_expr5);
			}
			MPConstraint c6 = solver.makeConstraint(0, infinity, String.format(
					"service_move_cost_constr_max_s%s_c", i_service_id));
			StringBuilder sb_expr6 = new StringBuilder();
			if (saveLP) {
				sb_expr6.append(String.format(
						"service_move_cost_constr_max_s%s_c: ", i_service_id));
			}
			c6.setCoefficient(abs_se[i - 1], 1.0);
			if (saveLP) {
				sb_expr6.append(String.format(" + %.1f abs_se_s%d ", 1.0,
						iminus1_service_id));
			}
			c6.setCoefficient(se_cost[i - 2], -1.0);
			if (saveLP) {
				sb_expr6.append(String.format(" + %.1f se_cost_s%d ", -1.0,
						iminus2_service_id));
			}
			c6.setCoefficient(se[i], 1.0);
			if (saveLP) {
				sb_expr6.append(String.format(" + %.1f se_s%s ", 1.0,
						i_service_id));
				sb_expr6.append(String.format(">= %d\n", 0));
				ipModelAsString.append(sb_expr6);
			}
		}
		// log.debug("Soft Cosntraint 3 added");
		// point.collect();
	}

	// machine move cost
	private void addSoftConstraints4() {
		// EtmPoint point = etmMonitor
		// .createPoint("OrtExtraLeanIPModelBuilder:addSoftConstraints4");
		int rhs = 0;
		MPConstraint c = solver.makeConstraint("machine_move_cost");
		if (saveLP) {
			ipModelAsString.append(String.format("machine_move_cost: "));
		}
		for (Integer p : selectedProcesses) {
			Process selectedProcess = problem.getProcesses().get(p);
			int im = initial_solution.getMachineIdForProcessId(p);
			Machine aMachine = problem.getMachines().get(im);
			for (Integer m : selectedProcess.getEligibleMachines()) {
				if (im == m.intValue() || aMachine.getMachineMoveCost(m) == 0)
					continue;
				MPVariable xVar = getxVar(p, m);
				c.setCoefficient(xVar, aMachine.getMachineMoveCost(m));
				if (saveLP) {
					ipModelAsString.append(String.format(" + %d x%d_%d ",
							aMachine.getMachineMoveCost(m), p, m));
				}
			}
		}
		for (int p = 0; p < problem.P; p++) {
			if (selectedProcesses.contains(new Integer(p)))
				continue;
			int im = initial_solution.getMachineIdForProcessId(p);
			int cm = current_solution.getMachineIdForProcessId(p);
			if (im != cm) {
				Machine aMachine = problem.getMachines().get(im);
				rhs -= aMachine.getMachineMoveCost(cm);
			}
		}
		c.setCoefficient(mm_cost, -1.0);
		c.setBounds(rhs, rhs);
		if (saveLP) {
			ipModelAsString.append(String.format(" + %.1f mm_cost ", -1.0));
			ipModelAsString.append(String.format("= %d", rhs));
		}
		// log.debug("Soft Constraints 4 added");
		// point.collect();
	}

	public Solution saveToSolution() {
		Solution solution = new Solution(problem);
		for (int p = 0; p < problem.P; p++) {
			for (int m = 0; m < problem.M; m++) {
				if (getxVar(p, m) != null) {
					double x_value = getxVar(p, m).solutionValue();
					if (x_value > 0.9999 && x_value < 1.0001) {
						solution.assignProcessToMachine(p, m);
					}
				} else {
					if (current_solution.getMachineIdForProcessId(p) == m) {
						solution.assignProcessToMachine(p, m);
					}
				}
			}
		}
		free();
		return solution;
	}

	public void updateSolution(SolverEnvironment env,
			List<Integer> selectedProcesses) {
		for (Integer p : selectedProcesses) {
			Process aProcess = problem.getProcesses().get(p);
			for (Integer m : aProcess.getEligibleMachines()) {
				double x_value = getxVar(p, m).solutionValue();
				if (x_value > 0.9999 && x_value < 1.0001) {
					int old_m_id = env.getPotentialSolution()
							.getMachineIdForProcessId(p);
					if (old_m_id != m.intValue()) {
						env.getCostCalculator().getProcessMoveToMachineCost(
								env.getPotentialSolution(), p, old_m_id, m);
						IncrementalMoveProcessToMachineCommand cmd = new IncrementalMoveProcessToMachineCommand(
								env);
						cmd.setM_id(m);
						cmd.setP_id(p);
						env.getInvoker().addCommand(cmd);
						env.getInvoker().executeLastCommand();
						env.getCostCalculator().updateNewSolutionCost();
					}
				}
			}
		}
	}

	public void updateSolutionDirect(SolverEnvironment env,
			List<Integer> selectedProcesses) {
		for (Integer p : selectedProcesses) {
			Process aProcess = problem.getProcesses().get(p);
			for (Integer m : aProcess.getEligibleMachines()) {
				double x_value = getxVar(p, m).solutionValue();
				if (x_value > 0.9999 && x_value < 1.0001) {
					int old_m_id = env.getPotentialSolution()
							.getMachineIdForProcessId(p);
					if (old_m_id != m.intValue()) {
						env.getCostCalculator().getProcessMoveToMachineCost(
								env.getPotentialSolution(), p, old_m_id, m);
						env.getPotentialSolution().moveProcess(p, old_m_id, m);
						env.getCostCalculator().updateServiceMove(p, old_m_id,
								m);
						env.getCostCalculator().updateNewSolutionCost();
					}
				}
			}
		}
	}

	private MPVariable getxVar(int process_id, Integer machine_id) {
		int xvar_process_index = selectedProcesses.indexOf(new Integer(
				process_id));
		if (xvar_process_index == -1) {
			return null;
		} else {
			return x[xvar_process_index].get(machine_id);
		}
	}

	public long computeAndGetCostOverIPModel() {
		computeLoadCost();
		computeBalanceCost();
		computeProcessMoveCost();
		computeServiceMoveCost();
		computeMachineMoveCost();
		return load_cost + balance_cost + process_move_cost + service_move_cost
				+ machine_move_cost;
	}

	// #######################################################################

	
	public String getSolverVersion() {
		return solver.solverVersion();
	}

	private void saveToLP(String fn) {
		FileWriter outFile;
		try {
			outFile = new FileWriter(fn);
			PrintWriter out = new PrintWriter(outFile);
			out.print(ipModelAsString.toString().replaceAll("[\\+]\\s[\\-]",
					"- "));
			out.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void free() {
		solver.clear();
		solver.delete();
	}

	// #####################################################################

	private void displaySystemMemoryInfo() {
		Runtime runtime = Runtime.getRuntime();
		NumberFormat format = NumberFormat.getInstance();
		StringBuilder sb = new StringBuilder();
		long maxMemory = runtime.maxMemory();
		long allocatedMemory = runtime.totalMemory();
		long freeMemory = runtime.freeMemory();
		sb.append("free memory: " + format.format(freeMemory / 1024) + " ");
		sb.append("allocated memory: " + format.format(allocatedMemory / 1024)
				+ " ");
		sb.append("max memory: " + format.format(maxMemory / 1024) + " ");
		sb.append("total free memory: "
				+ format.format((freeMemory + (maxMemory - allocatedMemory)) / 1024)
				+ " ");
		System.out.println(sb.toString());
	}

	public double getSolverObjectiveValue() {
		return solver.objective().value();
	}
}
